// png2jng
//Remarks: initially I supposed to support 12 bits JPEG (encoded from 16 bit PNG), but these formats not popular - so may be it will be added later... or never...

#pragma warning(push)
#pragma warning(disable:4996)

#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>

#define MNG_SUPPORT_READ
#define	MNG_SUPPORT_WRITE
#define MNG_ACCESS_CHUNKS
#define MNG_STORE_CHUNKS

#if defined(DEBUG) || defined(_DEBUG)
#define DUMP_IMAGES
#endif

extern "C"
{
#include "libmng.h"
#include "libmng_data.h"
#include "libmng_error.h"

#include "png.h"

#include "jpeglib.h"
#include "jerror.h"

#include "zlib.h"
#include "commander.h"
}

#include "image.h"
#include "helpers.h"
#include "myerrors.h"

//////////////////////////////////////////////////////////////////////////
//string constants
const TCHAR* strWarnNoAlpha = _T("Source image hasn't alpha channel!");

const TCHAR* strMsgStarting = _T("Converting file ");
const TCHAR* strMsgFinished = _T("Success! Output file saved to ");
const TCHAR* strMsgHello = _T("PNG to JNG converter.");

const TCHAR* strMsgInputFileSize =  _T("Input file size:  ");
const TCHAR* strMsgOutputFileSize = _T("Output file size: ");

//////////////////////////////////////////////////////////////////////////
//forward declarations                                                  //
//////////////////////////////////////////////////////////////////////////

void loadImage(const TCHAR* fileName, Image* &rgbImage, Image* &alphaImage, bool &oneBitTrans);
void saveImageJNG(const TCHAR* fileName, const Image* rgbImage, const Image* alphaImage, int rgbQuality, int alphaQuality, int alphaBitsNum);

//////////////////////////////////////////////////////////////////////////
//Decoding                                                              //
//////////////////////////////////////////////////////////////////////////

static const unsigned char  pngSignature[8] = {137, 80, 78, 71, 13, 10, 26, 10};

void png_silent_error(png_struct_def* png_ptr, char* message){}
void png_silent_warning(png_struct_def* png_ptr, char* message){}
void png_stream_reader( png_structp png_ptr, png_bytep data, png_size_t length)
{
	FILE* inFile = reinterpret_cast<FILE*>(png_ptr->io_ptr);

	size_t ret = fread(data, 1, length, inFile);
	if(ret <= 0)
		png_error(png_ptr,"Read Error");
}

int isPng(const unsigned char* data)
{
	for(int i=0; i<8; i++)
	{
		if(data[i]!=pngSignature[i])
		{
			return 0;
		}
	}

	return 1;
}

void loadImage(const TCHAR* fileName, Image* &rgbImage, Image* &alphaImage, bool &oneBitTrans)
{
	int srcWidth = 0;
	int srcHeight = 0;
	FILE* inFile = NULL;
	int bitDepth = -1;
	int colorType = -1;
	int interlaceType = -1;
	int compressionMethod = -1;
	int filterMethod = -1;
	
	inFile = _tfopen(fileName, _T("rb"));
	if(inFile==NULL)
	{
		showErrorAndExit(strErrOpengInputFile, ERROR_OPEN_INPUT_FILE);
	}

	unsigned char sigBuffer[sizeof(pngSignature)];

	size_t readRes = fread(sigBuffer, 1, sizeof(sigBuffer), inFile);

	if(readRes!=sizeof(sigBuffer))
	{
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	if(!isPng(sigBuffer))
	{
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	fseek(inFile, 0, SEEK_SET);

	png_structp png_ptr;
	png_infop info_ptr;

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
	if (png_ptr == NULL)
	{
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	png_set_error_fn(png_ptr,NULL,(png_error_ptr)png_silent_error,(png_error_ptr)png_silent_warning);

	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL)
	{
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	png_set_read_fn(png_ptr, inFile, png_stream_reader);

	png_read_info(png_ptr, info_ptr);
	png_get_IHDR(png_ptr, info_ptr, (png_uint_32*)&srcWidth, (png_uint_32*)&srcHeight, &bitDepth, &colorType,
				&interlaceType, &compressionMethod, &filterMethod);

	/*if(bitDepth!=8 && bitDepth!=16)
	{
		// Unknown color type
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(inFile);
		showErrorAndExit(strErrUnsupportedPngFormat, ERROR_UNSUPPORTED_PNG_FORMAT);
	}*/

	png_set_strip_16(png_ptr);// 16-bit RGB data will be converted to 8-bit RGB (RRGGBB -> RGB)


	if (colorType == PNG_COLOR_TYPE_PALETTE) 
	{
		png_set_palette_to_rgb(png_ptr);
	}

	if (colorType == PNG_COLOR_TYPE_GRAY)
	{
		png_set_gray_to_rgb(png_ptr);//TODO: fix this in future - pack greyscale PNGs as greyscale JPEGs
	}

	if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)
		png_set_gray_1_2_4_to_8(png_ptr);

	if (bitDepth < 8)
        png_set_packing(png_ptr);//Extract multiple pixels with bit depths of 1, 2, and 4 from a single byte into separate bytes

	png_bytep trans;
	int numTrans;
	png_color_16p transValues;
	bool hasTransparency = false;
	
	oneBitTrans = false;

	if (png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, &transValues))
	{
		//check if we has transparecies in palleted image
		for (int i = 0; i < numTrans; ++i)
		{
			if (trans[i] != 0xff)
			{
				hasTransparency = true;
				oneBitTrans = true;//if we have only one transparent color we can automatically pack alpha channel to one bit per channel format
			}

			if(trans[i] > 0 && trans[i] < 255)
				oneBitTrans = false;
		}
	}

	int colorChannelsNum = 3;

	if (colorType == PNG_COLOR_TYPE_PALETTE && hasTransparency || colorType == PNG_COLOR_TYPE_RGB_ALPHA) 
	{
		png_read_update_info(png_ptr, info_ptr);
		colorChannelsNum = 4;
	}
	else if (colorType == PNG_COLOR_TYPE_RGB || (colorType == PNG_COLOR_TYPE_PALETTE && !hasTransparency) || colorType == PNG_COLOR_TYPE_GRAY)
	{
		//png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
		png_read_update_info(png_ptr, info_ptr);
	}
	else
	{
		// unsupported color type
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(inFile);
		showErrorAndExit(strErrDecodeInputFile, ERROR_DECODE_INPUT_FILE);
	}

	unsigned char* rowData = new unsigned char[srcWidth * (bitDepth >> 3) * colorChannelsNum];

	if(!rowData)
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(inFile);
		showErrorAndExit(strErrOutOfMem, ERROR_OUT_OF_MEM);
	}

	rgbImage = Image::create(srcWidth, srcHeight, Image::FORMAT_RGB, bitDepth);
	if(!rgbImage)
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		fclose(inFile);
		showErrorAndExit(strErrOutOfMem, ERROR_OUT_OF_MEM);
	}

	if(colorType == PNG_COLOR_TYPE_PALETTE && hasTransparency || colorType == PNG_COLOR_TYPE_RGB_ALPHA)
	{
		alphaImage = Image::create(srcWidth, srcHeight, Image::FORMAT_GREYSCALE, bitDepth);
		if(!alphaImage)
		{
			png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
			fclose(inFile);
			showErrorAndExit(strErrOutOfMem, ERROR_OUT_OF_MEM);
		}
	}
	else
	{
		alphaImage = NULL;
		showWarning(strWarnNoAlpha);
	}

	unsigned char* rbgDest = (unsigned char*)rgbImage->getPixels();
	unsigned char* alphaDest = (alphaImage!=0) ? (unsigned char*)alphaImage->getPixels() : NULL;

	for(int i=0; i<srcHeight; i++)
	{
		png_read_row(png_ptr, (png_bytep)rowData, NULL);
		if(bitDepth==8)
		{
			unsigned char* rbgDestRow = rbgDest;
			unsigned char* alphaDestRow = alphaDest;
			unsigned char* srcPtr = rowData;
			for(int j=0; j<srcWidth; j++)
			{
				*(rbgDestRow++) = *(srcPtr++);
				*(rbgDestRow++) = *(srcPtr++);
				*(rbgDestRow++) = *(srcPtr++);
				if(colorType == PNG_COLOR_TYPE_PALETTE && hasTransparency || colorType == PNG_COLOR_TYPE_RGB_ALPHA)
				{
					*(alphaDestRow++) = *(srcPtr++);
				}
			}

		}
		else if(bitDepth==16)
		{
			unsigned short* rbgDestRow = (unsigned short*)rbgDest;
			unsigned short* alphaDestRow = (unsigned short*)alphaDest;
			unsigned short* srcPtr = (unsigned short*)rowData;
			for(int j=0; j<srcWidth; j++)
			{
				*(rbgDestRow++) = *(srcPtr++);
				*(rbgDestRow++) = *(srcPtr++);
				*(rbgDestRow++) = *(srcPtr++);
				if(colorType == PNG_COLOR_TYPE_PALETTE && hasTransparency || colorType == PNG_COLOR_TYPE_RGB_ALPHA)
				{
					*(alphaDestRow++) = *(srcPtr++);
				}
			}
		}

		rbgDest += rgbImage->getPitch();
		if(alphaDest!=NULL)
			alphaDest += alphaImage->getPitch();
	}

	delete[] rowData;

	size_t inSize = ftell(inFile);
	showMessage(strMsgInputFileSize, inSize);
	fclose(inFile);

#ifdef DUMP_IMAGES
	rgbImage->dumpImage(_T("rgb.tga"));
	if(alphaImage!=NULL)
		alphaImage->dumpImage(_T("alpha.tga"));
#endif
}

//////////////////////////////////////////////////////////////////////////
//Encoding                                                              //
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
//mng
static mng_ptr mymngalloc(mng_uint32 size)
{
	return (mng_ptr)calloc(1, size);
}

static void mymngfree(mng_ptr p, mng_uint32 size)
{
	free(p);
}

static mng_bool mymngopenstreamwrite(mng_handle mng) { return MNG_TRUE; }
static mng_bool mymngclosestream(mng_handle mng)     { return MNG_TRUE; }

static mng_bool mymngwritestream (mng_handle mng, mng_ptr buf, mng_uint32 size, mng_uint32 *written)
{
	FILE *outFile = (FILE*)mng_get_userdata(mng);
	// write data
	size_t writeRes = fwrite(buf, 1, size, outFile);
	if(written!=NULL)
		*written = writeRes;
	
	return (size==0)? MNG_TRUE : (writeRes > 0);
}

//////////////////////////////////////////////////////////////////////////
//jpg
struct my_jpg_error_mgr
{
	struct jpeg_error_mgr pub;	//"public" fields
	jmp_buf setjmp_buffer;	//allows to return to caller in case of error
};

typedef struct my_jpg_error_mgr *my_jpg_error_ptr;

static void my_jpg_error_exit (j_common_ptr cinfo)
{
	my_jpg_error_ptr myErrorrMgr = (my_jpg_error_ptr) cinfo->err;

	longjmp(myErrorrMgr->setjmp_buffer, 1);
}

/////

static const int JPG_BUFFER_GRANULARITY = 4096*4;

struct my_jpg_dest_buffer
{
	unsigned char*	data;
	size_t			length;
	size_t			written;

	my_jpg_dest_buffer() {data = NULL; length = 0; written = 0;}
	~my_jpg_dest_buffer() {free(data); data = NULL; length = 0; written = 0;}
};

typedef my_jpg_dest_buffer *my_jpg_dest_buffer_ptr;

struct my_jpeg_destination_mgr
{
	jpeg_destination_mgr	pub;//public fields
	my_jpg_dest_buffer_ptr	destBuffer;
};

typedef my_jpeg_destination_mgr *my_jpeg_destination_mgr_ptr;

static void my_jpg_init_destination(j_compress_ptr cinfo)
{
	my_jpeg_destination_mgr_ptr dest = (my_jpeg_destination_mgr_ptr)cinfo->dest;

	dest->destBuffer->data = (unsigned char*)malloc(sizeof(JOCTET) * JPG_BUFFER_GRANULARITY);
	dest->destBuffer->length = sizeof(JOCTET) * JPG_BUFFER_GRANULARITY;

	dest->pub.next_output_byte = dest->destBuffer->data;
	dest->pub.free_in_buffer = JPG_BUFFER_GRANULARITY;
}

static boolean my_jpg_empty_output_buffer(j_compress_ptr cinfo)
{
	my_jpeg_destination_mgr_ptr dest = (my_jpeg_destination_mgr_ptr)cinfo->dest;

	dest->destBuffer->data = (unsigned char*)realloc(dest->destBuffer->data, dest->destBuffer->length + sizeof(JOCTET) * JPG_BUFFER_GRANULARITY);

	if (dest->destBuffer->data==NULL)
	{
		showErrorAndExit(strErrOutOfMem, ERROR_OUT_OF_MEM);
	}

	dest->pub.next_output_byte = dest->destBuffer->data + dest->destBuffer->length;
	dest->pub.free_in_buffer = sizeof(JOCTET) * JPG_BUFFER_GRANULARITY;
	dest->destBuffer->length += sizeof(JOCTET) * JPG_BUFFER_GRANULARITY;
	return true;
}

static void my_jpg_term_destination(j_compress_ptr cinfo)
{
	my_jpeg_destination_mgr_ptr dest = (my_jpeg_destination_mgr_ptr)cinfo->dest;
	
	dest->destBuffer->written = dest->destBuffer->length - dest->pub.free_in_buffer;
}

static void set_jpeg_buffer_dest(j_compress_ptr cinfo, my_jpg_dest_buffer_ptr destBuffer)
{
	my_jpeg_destination_mgr_ptr dest;

	if(cinfo->dest == NULL)
	{
		cinfo->dest = (jpeg_destination_mgr*)(*cinfo->mem->alloc_small)
						((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(my_jpeg_destination_mgr));
	}

	dest = (my_jpeg_destination_mgr_ptr)cinfo->dest;
	dest->pub.init_destination = my_jpg_init_destination;
	dest->pub.empty_output_buffer = my_jpg_empty_output_buffer;
	dest->pub.term_destination = my_jpg_term_destination;
	dest->destBuffer = destBuffer;
}

/////

void saveImageJNG(const TCHAR* fileName, const Image* rgbImage, const Image* alphaImage, int rgbQuality, int alphaQuality, int alphaBitsNum)
{
	if(rgbImage->getBitsPerChannel()!=8)
	{
		showErrorAndExit(strErrUnsupported16Bits, ERROR_UNSUPPORTED_16_BITS_PER_CHANNEL);
	}

	FILE* outFile = NULL;
	
	outFile = _tfopen(fileName, _T("wb"));
	if(outFile==NULL)
	{
		showErrorAndExit(strErrOpengOutputFile, ERROR_OPEN_OUTPUT_FILE);
	}

	mng_handle mngHandle = mng_initialize (outFile, mymngalloc, mymngfree, MNG_NULL);

	if(mngHandle==NULL)
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	//setting IO handlers
	if ((mng_setcb_openstream (mngHandle, mymngopenstreamwrite) != MNG_NOERROR) ||
			(mng_setcb_closestream (mngHandle, mymngclosestream) != MNG_NOERROR) ||
			(mng_setcb_writedata (mngHandle, mymngwritestream) != MNG_NOERROR))
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	//creating MNG
	if (mng_create(mngHandle) != MNG_NOERROR)
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	if(alphaImage == NULL)
		alphaBitsNum = 0;

	if (mng_putchunk_jhdr (mngHandle, rgbImage->getWidth(), rgbImage->getHeight(),
			(alphaImage!=NULL) ? MNG_COLORTYPE_JPEGCOLORA 
			: MNG_COLORTYPE_JPEGCOLOR,
			MNG_BITDEPTH_JPEG8, MNG_COMPRESSION_BASELINEJPEG,
			MNG_INTERLACE_SEQUENTIAL,
			alphaBitsNum, 
			(alphaImage!=NULL) ? ((alphaQuality > 0) ? MNG_COMPRESSION_BASELINEJPEG : 0):0, 
			0, 0) != MNG_NOERROR)
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	if (alphaImage!=NULL)
	{
		if(alphaImage->getFormat()!=Image::FORMAT_GREYSCALE)
		{
			fclose(outFile); outFile = NULL;
			showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
		}

		if (alphaQuality >= 0)
		{
			//compress alpha pixels s greyscale jpeg
			jpeg_compress_struct cinfo;
			my_jpg_error_mgr jpgErr;
			my_jpg_dest_buffer destBuffer;//buffer to hold compressed data

			cinfo.err = jpeg_std_error (&jpgErr.pub);
			jpgErr.pub.error_exit = my_jpg_error_exit;

			if (setjmp (jpgErr.setjmp_buffer))
			{
				fclose(outFile); outFile = NULL;
				showErrorAndExit(strErrSystem, ERROR_SYSTEM);
			}

			jpeg_create_compress(&cinfo);

			set_jpeg_buffer_dest(&cinfo, &destBuffer);

			cinfo.image_width = alphaImage->getWidth();
			cinfo.image_height = alphaImage->getHeight();
			cinfo.input_components = 1;
			cinfo.in_color_space = JCS_GRAYSCALE;

			jpeg_set_defaults(&cinfo);
			jpeg_set_quality(&cinfo, alphaQuality, true);
			jpeg_start_compress (&cinfo, true);

			JSAMPROW rowPointer[1];

			unsigned char *imageData = (unsigned char*)alphaImage->getPixels();

			while(cinfo.next_scanline < cinfo.image_height)
			{
				rowPointer[0] = (JSAMPLE*)imageData;
				jpeg_write_scanlines (&cinfo, rowPointer, 1);
				imageData += alphaImage->getPitch();
			}

			jpeg_finish_compress(&cinfo);
			jpeg_destroy_compress(&cinfo);

			if(destBuffer.written <= 0)
			{
				fclose(outFile); outFile = NULL;
				showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
			}

			if(mng_putchunk_unknown(mngHandle, MNG_UINT_JDAA, (mng_uint32)destBuffer.written, destBuffer.data) != MNG_NOERROR)
			{
				fclose(outFile); outFile = NULL;
				showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
			}
		}
		else
		{
			//store alpha with loseless compression
			unsigned char *tmpBuffer = NULL;
			size_t rowSizeInBits = alphaImage->getWidth() * alphaBitsNum;
			size_t rowSizeInBytes = (rowSizeInBits >> 3) + (rowSizeInBits & 7 ? 1 : 0);
			int tmpBufferSize = (rowSizeInBytes + 1) * alphaImage->getHeight();

			tmpBuffer = new unsigned char[tmpBufferSize];
			if(tmpBuffer==NULL)
			{
				fclose(outFile);
				showErrorAndExit(strErrOutOfMem, ERROR_OUT_OF_MEM);
			}

			//copy image rows and add filter type at row start (current filter type - no filters)
			unsigned char *tmpBufferPtr = tmpBuffer;
			unsigned char *srcPtr = (unsigned char*)alphaImage->getPixels();
			for(int rowNum = 0; rowNum < alphaImage->getHeight(); rowNum++)
			{
				*tmpBufferPtr = PNG_NO_FILTERS;
				tmpBufferPtr++;
				if(alphaBitsNum == 8)
				{
					//copy alpha data "as is"
					memcpy(tmpBufferPtr, srcPtr, alphaImage->getWidth());
					tmpBufferPtr += alphaImage->getWidth();
				}
				else
				{
					/*
					Pack alpha pixels.
					Pixels smaller than a byte never cross byte boundaries; they are packed into bytes with the leftmost pixel in the high-order bits of a byte, the rightmost in the low-order bits.
					*/
					memset(tmpBufferPtr, 0, rowSizeInBytes);
					int shiftVal = 8 - alphaBitsNum;
					unsigned char* srcRowPtr = srcPtr;
					for(int n = 0; n < alphaImage->getWidth(); n++)
					{
						unsigned char val = (*srcRowPtr++) >> (8 - alphaBitsNum);
						*tmpBufferPtr |= val << shiftVal;
						shiftVal -= alphaBitsNum;
						if(shiftVal < 0)
						{
							tmpBufferPtr++;
							shiftVal = 8 - alphaBitsNum;
						}
					}
					if(shiftVal != 8 - alphaBitsNum)
						tmpBufferPtr++;

				}
				srcPtr += alphaImage->getPitch();
			} 

			//compress image rows
			z_stream zStream;

			zStream.zfree = (free_func)NULL;
			zStream.zalloc = (alloc_func)NULL;

			zStream.next_in = (Byte*)tmpBuffer;
			zStream.avail_in = tmpBufferSize;
			
			//initialize deflate with best compression level (9)
			if(deflateInit(&zStream, 9) != Z_OK)
			{
				delete[] tmpBuffer; tmpBuffer = NULL;
				fclose(outFile); outFile = NULL;
				showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);	
			}

			const int compressedBufSize = 8192;
			unsigned char compressedDataBuf[compressedBufSize];

			for(;;)
			{
				zStream.next_out = (Byte*)compressedDataBuf;
				zStream.avail_out = compressedBufSize;

				int retVal = deflate(&zStream, Z_FINISH);//compress portion of alpha data
				size_t compressedSize = compressedBufSize - zStream.avail_out;

				//put compressed alpha to JNG file
				if(mng_putchunk_idat(mngHandle, (mng_uint32)compressedSize, &compressedDataBuf) != MNG_NOERROR)
				{
					delete[] tmpBuffer; tmpBuffer = NULL;
					fclose(outFile); outFile = NULL;
					showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);	
				}

				if(retVal == Z_STREAM_END)
					break;
			}

			deflateEnd (&zStream);
			delete[] tmpBuffer; tmpBuffer = NULL;
		}
	}

	//here we will compress RGB data as JPEG and put it to JNG

	jpeg_compress_struct cinfo;
	my_jpg_error_mgr jpgErr;
	my_jpg_dest_buffer destBuffer;//buffer to hold compressed data

	cinfo.err = jpeg_std_error (&jpgErr.pub);
	jpgErr.pub.error_exit = my_jpg_error_exit;

	if (setjmp (jpgErr.setjmp_buffer))
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrSystem, ERROR_SYSTEM);
	}

	jpeg_create_compress(&cinfo);

	set_jpeg_buffer_dest(&cinfo, &destBuffer);

	cinfo.image_width = rgbImage->getWidth();
	cinfo.image_height = rgbImage->getHeight();
	cinfo.input_components = 3;
	cinfo.in_color_space = JCS_RGB;

	jpeg_set_defaults(&cinfo);
	jpeg_set_quality(&cinfo, rgbQuality, true);
	jpeg_start_compress (&cinfo, true);

	JSAMPROW rowPointer[1];

	unsigned char *imageData = (unsigned char*)rgbImage->getPixels();

	while(cinfo.next_scanline < cinfo.image_height)
	{
		rowPointer[0] = (JSAMPLE*)imageData;
		jpeg_write_scanlines (&cinfo, rowPointer, 1);
		imageData += rgbImage->getPitch();
	}

	jpeg_finish_compress(&cinfo);
	jpeg_destroy_compress(&cinfo);

	if(destBuffer.written <= 0)
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	if(mng_putchunk_jdat(mngHandle, (mng_uint32)destBuffer.written, destBuffer.data) != MNG_NOERROR)
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	if (mng_putchunk_iend (mngHandle) != MNG_NOERROR)
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	if (mng_write (mngHandle) != MNG_NOERROR)
	{
		fclose(outFile); outFile = NULL;
		showErrorAndExit(strErrEncoding, ERROR_ENCODE_OUTPUT);
	}

	mng_cleanup (&mngHandle);
	size_t outSize = ftell(outFile);
	showMessage(strMsgOutputFileSize, outSize);
	fclose(outFile); outFile = NULL;
	//That's all Folks!
}

//////////////////////////////////////////////////////////////////////////
// argument parsing
//////////////////////////////////////////////////////////////////////////

int handle_arg_file_name(struct command *self, void* param)
{
	*((const TCHAR**)param) = (const TCHAR*)self->arg;

	return 1;
}

int handle_arg_jpeg_quality(struct command *self, void* param)
{
	if(_stscanf_s(self->arg, _T("%d"), param)!=1 || *((int*)param) < 0 || *((int*)param) > 100)
	{
		showErrorAndExit(strErrWrongArgQuality, ERROR_ARGUMENTS_VALUES);
	}

	return 1;
}

int handle_arg_alpha_bits(struct command *self, void* param)
{
	if((_stscanf_s(self->arg, _T("%d"), (int*)param)!=1) || ((*((int*)param) != 1) && (*((int*)param) != 2) && (*((int*)param) != 4) && (*((int*)param) != 8)))
	{
		showErrorAndExit(strErrWrongBitsCount, ERROR_ARGUMENTS_VALUES);
	}

	return 1;
}

//////////////////////////////////////////////////////////////////////////
// main
//////////////////////////////////////////////////////////////////////////

int _tmain(int argc, _TCHAR* argv[])
{
	const TCHAR*	inFileName = NULL;
	const TCHAR*	outFileName = NULL;
	int				rgbQuality = -1;
	int				alphaQuality = -1;
	int				alphaBits = -1;
	bool			srcHasOneBitTrans = false;

	showMessage(strMsgHello);

	command_t cmd;
	command_init(&cmd, argv[0], _T("1.0.3"));

	command_option(&cmd, _T("-i"), _T("--in <file_name>"), _T("input png file."), handle_arg_file_name, &inFileName);
	command_option(&cmd, _T("-o"), _T("--out <file_name>"), _T("output jng file."), handle_arg_file_name, &outFileName);

	command_option(&cmd, _T("-c"), _T("--color_quality <quality>"), _T("jpeg quality for RGB channels."), handle_arg_jpeg_quality, &rgbQuality);
	command_option(&cmd, _T("-a"), _T("--alpha_quality [quality]"), _T("jpeg quality for Alpha channel (if not specified - alpha channel will use lossless compression)."), handle_arg_jpeg_quality, &alphaQuality);

	command_option(&cmd, _T("-b"), _T("--bits [bits_num]"), _T("bits per alpha channel (available options: 1, 2, 4, 8)."), handle_arg_alpha_bits, &alphaBits);

	command_parse(&cmd, argc, argv);

	command_deinit(&cmd);

	if(alphaBits > 0 && (alphaQuality > 0 && alphaBits != 8 ))
	{
		showErrorAndExit(strErrWrongAlphaArgs, ERROR_ARGUMENTS_VALUES);
	}

	Image *rgbImage = NULL, *alphaImage = NULL;
	
	showMessage(strMsgStarting, inFileName);

	loadImage(inFileName, rgbImage, alphaImage, srcHasOneBitTrans);

	if(alphaBits < 0)
	{
		alphaBits = srcHasOneBitTrans ? 1 : 8;
	}
	saveImageJNG(outFileName, rgbImage, alphaImage, rgbQuality, alphaQuality, alphaBits);

	delete rgbImage;
	delete alphaImage;

	showMessage(strMsgFinished, outFileName);
	
	return 0;
}

#pragma warning(pop)